package com.jh.netty.instruction;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.Builder;
import lombok.Data;
import lombok.Singular;

import java.math.BigInteger;


/**
 * <h3>通讯包</h3>
 *
 * <table>
 *     <tr>
 *         <td>  引导码  </td>
 *         <td>  设备码  </td>
 *         <td>  控制器地址码  </td>
 *         <td>  数据包长度  </td>
 *         <td>  数据包  </td>
 *         <td>  校验码  </td>
 *     </tr>
 *     <tr>
 *         <td>  2byte  </td>
 *         <td>  1byte  </td>
 *         <td>  1byte  </td>
 *         <td>  2byte  </td>
 *         <td>  (长度可变)  </td>
 *         <td>  2byte  </td>
 *     </tr>
 * </table>
 * <br>
 * 引导码   设备码  控制器地址码   数据包长度   数据包       校验码
 * <br>
 * 2byte    1byte    1byte        2byte    (长度可变)    2byte
 *
 * <ul>
 *     <li>引导码：2 byte = 0x5252</li>
 *     <li>设备码：1 byte = 0x01</li>
 *     <li>控制器地址码：1 byte = 0x01 - 0xFE(0xFF为广播地址，0x00为系统保留)</li>
 *     <li>数据包长度：2 byte = 0 - 0x07FF，表示紧跟数据包长度</li>
 *     <li>数据包：（变长）  长度可变，由数据包长度指定</li>
 *     <li>校验码：2 byte = 计算从设备码开始到数据包最后一个字节的CRC16</li>
 * </ul>
 *
 * 数据包格式：
 * 命令码  数据码1 数据码2 ... 数据码n
 * 1 byte  （数据内容）
 *
 * 通信包中的数据都是16进制，不包括前缀Ox
 *
 * <h3>指令</h3>
 * <ul>
 *     <li>52 52 01 01 00 02 05 00 26 76    开门1</li>
 *     <li>52 52 01 01 00 02 05 01 36 57    开门2</li>
 *     <li>52 52 01 01 00 02 05 02 06 34    开门3</li>
 *     <li>52 52 01 01 00 02 05 03 16 15    开门4</li>
 *     <li>52 52 01 01 00 01 40 F0 62	读取门状态</li>
 * </ul>
 *
 *  结果 52 52 01 01 00 11 40 01 01 01 01 01 01 01 01 01 01 01 01 04 04 04 04 04 ce
 *  <br>
 *  结果 52 52 01 01 00 11 40 01 01 01 01 01 01 01 01 01 01 01 02 04 04 04 04 ea 1c
 *  <br>
 *  52 52 01 01 00 01 06 d8 60   读取门参数
 *
 */
@Builder
@Data
public class CommunicationPacket {

    public static final String DEFAULT_BOOT_CODE = "5252";

    /**
     * 引导码
     * 2 byte = 0x5252
     * 默认5252
     */
    @Builder.Default
    private String bootCode = DEFAULT_BOOT_CODE;

    /**
     * 设备码
     * 1 byte = 0x01
     */
    @Builder.Default
    private String equipmentCode = "01";

    /**
     * 控制器地址码
     * 1 byte = 0x01 – 0xFE（0xFF 为广播地址，0x00 为系统保留）
     * @see ControllerAddressCode
     */
    @Builder.Default
    private String controllerAddressCode = "01";

    /**
     * 数据包长度
     * 2 byte       = 0 – 0x07FF，表示紧跟数据包长度；
     * @see DataPacket#getDataPacketLength()
     * 由{@link DataPacket}计算，不建议主动设置
     */
    private String dataPacketLength;

    /**
     * 数据包 （）
     * 长度可变，由数据包长度指定；
     */
    private DataPacket dataPacket;

    /**
     * 校验码： 2 byte = 计算从设备码开始到数据包最后一个字节的 CRC16 码
     *
     * 通信包最后的 CRC16 校验码计算采用 CCITT 标准，初始值 0x5323。
     *
     * 通过{@link #calCRC16()}计算
     */
    private String crc16Code;

//    /**
//     * 设置数据包指令
//     * @param dataPacket 数据包指令  例如：0500
//     *
//     * 构造{@link DataPacket}
//     */
//    public void setDataPacketInstruct(String dataPacket) {
//        this.dataPacket = DataPacket.parse(dataPacket);
//    }

    /**
     * 通过解析指令构造 {@link CommunicationPacket} 通讯包
     * @param instruction 指令
     */
    public static CommunicationPacket parse(String instruction) {
        String instrut = instruction.replace(" ", "");

        // 5252 01 01 0002 0500 2676
        String bootCode = instrut.substring(0, 4);
        String equipmentCode = instrut.substring(4, 6);
        String controllerAddressCode = instrut.substring(6, 8);
        String dataPacketLength = instrut.substring(8, 12);

        String dataPacket = instrut.substring(12, instrut.length() - 4);
        String crc16 = instrut.substring(bootCode.length() + equipmentCode.length() +  controllerAddressCode.length() +
                dataPacketLength.length() + dataPacket.length());

        return CommunicationPacket.builder()
                .bootCode(bootCode)
                .equipmentCode(equipmentCode)
                .controllerAddressCode(controllerAddressCode)
                .dataPacketLength(dataPacketLength)
                .dataPacket(DataPacket.parse(dataPacket))
                .crc16Code(crc16)
                .build();

//        CommunicationPacket cp = new CommunicationPacket();
//        cp.setBootCode(bootCode);
//        cp.setEquipmentCode(equipmentCode);
//        cp.setControllerAddressCode(controllerAddressCode);
//        cp.setDataPacketLength(dataPacketLength);
//        cp.setDataPacketInstruct(dataPacket);
//        cp.setCrc16Code(crc16);
//        return cp;
    }

    /**
     * 通讯包转{@link ByteBuf},发送指令时使用
     */
    public ByteBuf toByteBuf() {
        ByteBuf bb = Unpooled.buffer();
        bb.writeBytes(hexString2Bytes(toInstructionText()));
        return bb;
    }

    /**
     * 转为指令字符串 16进制
     */
    public String toInstructionText() {
        return bootCode + equipmentCode + controllerAddressCode + dataPacketLength + dataPacket.getDataPacket() + crc16Code;
    }

    /**
     * 16进制字符串转字节数组
     * @param src 16进制字符串
     * @return 字节数组
     */
    public static byte[] hexString2Bytes(String src) {
        int l = src.length() / 2;
        byte[] ret = new byte[l];
        for (int i = 0; i < l; i++) {
//            ret[i] = Byte.valueOf(src.substring(i * 2, i * 2 + 2), 16);
            ret[i] = Integer.valueOf(src.substring(i * 2, i * 2 + 2), 16).byteValue();
        }
        return ret;
    }

//    /**
//     * 计算从设备码开始到数据包最后一个字节的 CRC16 码
//     */
//    public void checkCrc16() {
////        int equipmentCodeValue = Integer.parseInt( equipmentCode, 16);
////        int controllerCodeValue = Integer.parseInt(controllerAddressCode, 16);
////        int dataPacketLengthValue = Integer.parseInt(dataPacketLength, 16);
////        int dataPacketValue =
//
//        String str = equipmentCode + controllerAddressCode + dataPacketLength + dataPacket.getDataPacketStr();
//        // "01 01 00 01 40"
//        byte[] byteArr = new BigInteger(str.replace(" ", ""), 16).toByteArray();
//        int c = CRC16.CRC16_CCITT_FALSE(byteArr);
//        System.out.println(c);
//        System.out.println(Integer.toHexString(c));
//    }

    /**
     * 是否为有效的crc16码
     */
    public boolean isValidCRC16() {
        String str = equipmentCode + controllerAddressCode + dataPacketLength + dataPacket.getDataPacket();
        // "01 01 00 01 40"
        byte[] byteArr = new BigInteger(str.replace(" ", ""), 16).toByteArray();
        int c = CRC16.CRC16_CCITT_FALSE(byteArr);
//        String crc16Code = Integer.toHexString(c);
        //String.format("%04x", c) 16进制
        return crc16Code.equalsIgnoreCase(String.format("%04x", c));
    }

    /**
     * 计算crc16, 计算从设备码开始到数据包最后一个字节的 CRC16 码
     * 2 byte 两个字节
     */
    public void calCRC16() {
        String str = equipmentCode + controllerAddressCode + dataPacketLength + dataPacket.getDataPacket();
        // "01 01 00 01 40"
        byte[] byteArr = new BigInteger(str.replace(" ", ""), 16).toByteArray();
        int c = CRC16.CRC16_CCITT_FALSE(byteArr);
        this.crc16Code = String.format("%04x", c); //Integer.toHexString(c);
    }
}
